#include <QApplication>
#include <QWidgetList>
#include <QMessageBox>
#include "GlobPref.h"
#include "WinMain.h"
#include "SrvConnection.h"
#include "DlgServerConTest.h"
#include "RequestDescriptor.h"
#include "XmlHelper.h"
#include "XmlSrvHandler.h"
#include "ModTimeSlice.h"
#include "DataModel.h"
#include "scImpl.h"

DataModel * DataModel::inst(0);
std::string DataModel::strNoEntity("noentity");
std::string DataModel::strEntities("m_entities");
std::string DataModel::strEntityAttrs("m_entity_attrs");
QString DataModel::strNoEntityQ("noentity");

#define FCT "DataModel::get"
DataModel * DataModel::get()
{
	if(inst)
		return inst;
	static int recursionTester(0);
	if(++recursionTester>1)
		throw new AppException(FCT " recursive singleton creation", true);
	SrvConnection *srv(SrvConnection::createFromSettings());
	DlgServerConTest dlg(*srv, true);
	if(dlg.exec()!=QDialog::Accepted)
	{
		delete srv;
		GlobPref::get()->getMainWindow()->serverDataSl();
		srv=SrvConnection::createFromSettings();
	}
	inst=new DataModel(*srv);
	inst->postConstruct(); // Retrieval of meta data needs already the singleton to accessible through get
	--recursionTester;
	return inst;
}

#undef FCT
#define FCT "DataModel::entityId2Domain"
DataDomain DataModel::entityId2Domain(PkType entityId)
{
	if(bootTrans)
	{
		switch(entityId)
		{
		case EID_M_ENTITIES:
		case EID_M_ATTRS:
			return DOMAIN_METADATA;
		default: return DOMAIN_UNDEF;
		}
	}
	DTsCPkIdx & pks(dts.get<DataTable::PkTag>());
	DTsCPkIdx::const_iterator it(pks.find(entityId));
	if(it==pks.end())
		return DOMAIN_UNDEF;
	return it->getDomain();
}

#undef FCT
#define FCT "DataModel::entityId2DomainText"
QString DataModel::entityId2DomainText(PkType entityId)
{
	if(bootTrans)
	{
		switch(entityId)
		{
		case EID_M_ENTITIES:
		case EID_M_ATTRS:
			return XmlHelper::domain2DomainText(DOMAIN_METADATA);
		default: return QString("nodomain");
		}
	}
	DTsCPkIdx & pks(dts.get<DataTable::PkTag>());
	DTsCPkIdx::const_iterator it(pks.find(entityId));
	if(it==pks.end())
		return QString("nodomain");
	return XmlHelper::domain2DomainText(it->getDomain());
}

#undef FCT
#define FCT "DataModel::entityId2Text"
const std::string & DataModel::entityId2Text(PkType entityId) const
{
	if(bootTrans)
	{
		switch(entityId)
		{
		case EID_M_ENTITIES: return strEntities;
		case EID_M_ATTRS: return strEntityAttrs;
		default: return strNoEntity;
		}
	}
	DTsCPkIdx & pks(dts.get<DataTable::PkTag>());
	DTsCPkIdx::const_iterator it(pks.find(entityId));
	if(it==pks.end())
		return strNoEntity;
	return it->getTechName();
}


#undef FCT
#define FCT "DataModel::entityId2TextQ"
QString DataModel::entityId2TextQ(PkType entityId) const
{
	if(bootTrans)
	{
		switch(entityId)
		{
		case EID_M_ENTITIES: return QString("m_entities");
		case EID_M_ATTRS: return QString("m_entity_attrs");
		default: return strNoEntityQ;
		}
	}
	DTsCPkIdx & pks(dts.get<DataTable::PkTag>());
	DTsCPkIdx::const_iterator it(pks.find(entityId));
	if(it==pks.end())
		return strNoEntityQ;
	return it->getTechNameQ();
}
#undef FCT
#define FCT "DataModel::text2EntityId"
PkType DataModel::text2EntityId(QString entity) const
{
	if(bootTrans)
	{
		if(entity=="m_entities")
			return EID_M_ENTITIES;
		else if(entity=="m_entity_attrs")
			return EID_M_ATTRS;
		else
			return PK_UNDEF;
	}
	DTsCTechNameIdx & tns(dts.get<DataTable::TechNameTag>());
	DTsTechNameIdx::const_iterator it=tns.find(entity.toStdString());
	if(it==tns.end())
		return PK_UNDEF;
	return it->getPk();
}

#undef FCT
#define FCT "DataModel::DataModel"
DataModel::DataModel(SrvConnection & srvConnection) :
	srvCon(&srvConnection), timer(this),
	ticks(0), ticksAlive(300), ticksGarbage(310), tickGarbageFin(0), ticksUpdate(10000),
	syncTrans(0), bootTrans(true), queueing(true),
	deadline(QDateTime::currentDateTime()), rq("construction..."), data("construction...")
{
	connect(&timer, SIGNAL(timeout()), this, SLOT(timerTic()));
	// no need to connect testReadySig because connectionAvailableSig servers the same purpose
	// connect(srvCon, SIGNAL(testReadySig(bool, unsigned long)), this, SLOT(checkQueueingSl(bool)));
	connect(srvCon, SIGNAL(connectionAvailableSig(bool)), this, SLOT(checkQueueingSl(bool)));
	connect(srvCon, SIGNAL(dataReadySig(unsigned long)), this, SLOT(parseDataSl(unsigned long)));
	// we do not handle requestFailedSig - user shall start a reload
	//connect(srvCon, SIGNAL(requestFailedSig(unsigned long), this, SLOT());
	// we do not care for deleted transactions
	//connect(srvCon, SIGNAL(transactionDeleteSig(unsigned long), this, SLOT());
	// no idea yet how to handle this signals
	//connect(srvCon, SIGNAL(connectionShutdownSig(void), this, SLOT());
}

#undef FCT
#define FCT "DataModel::postConstruct"
void DataModel::postConstruct()
{
	if(!bootTrans)
		throw new AppException(FCT " called twice", true);
	while(!srvCon->acceptRequest())
	{
		if(QMessageBox::question(GlobPref::get()->getMainWindow(), tr("Server Connection"),
			tr("The server does not accept requests. Retry?"),
			QMessageBox::Yes | QMessageBox::Abort, QMessageBox::Yes)==QMessageBox::Abort)
			GlobPref::get()->getMainWindow()->close();
	}
	RequestDescriptor requDesc(Request(EID_M_ENTITIES));
	requDesc.include(Request(EID_M_ATTRS));
	ctrc(Trc::WORK,APP,FCT)<<"doing boot-request "<<requDesc<<endl;
	dumpRequest(&requDesc);
	syncTrans=srvCon->createTransactionId();
	unsigned long res(srvCon->startRequest(&requDesc, syncTrans, true));
	syncTrans=0;
	if(!res)
		queueing=false;
	timer.start(1000);
}

#undef FCT
#define FCT "DataModel::~DataModel"
DataModel::~DataModel()
{
	timer.stop();
	for(ModTimeSlices::iterator it(mts.begin()); it!=mts.end(); ++it)
		delete it->second;
}

#undef FCT
#define FCT "DataModel::setDeadline"
void DataModel::setDeadline(const QDateTime & newDeadline)
{
	if(deadline==newDeadline)
		return; // avoid lengthy updates if nothing changed
	deadline=newDeadline;
	emit deadlineChangedSig();
}

#undef FCT
#define FCT "DataModel::requestData"
unsigned long DataModel::requestData(RequestDescriptor * requDesc)
{
	ctrc(Trc::LOOP,APP,FCT)<<"original request "<<*requDesc<<endl;
	if(!bootTrans)
		reduceRequest(requDesc);
	if(requDesc->empty())
	{ // no dataChangedSig();
		ctrc(Trc::LOOP,APP,FCT)<<"reduced to empty request (done)"<<endl;
		delete requDesc;
		return 0; // we serve synchronously
	}
	unsigned long res(0);
	if(queueing || !srvCon->acceptRequest())
	{
		ctrc(Trc::WORK,APP,FCT)<<"queuing request "<<*requDesc<<(queueing?" (planned)":" (not accepting)")<<endl;
		res=srvCon->createTransactionId();
		requDesc->setTransId(res);
		rqQueue.push_back(requDesc);
		if(!queueing)
			keepAlive();
	}
	else
	{
		ctrc(Trc::WORK,APP,FCT)<<"doing request "<<*requDesc<<endl;
		dumpRequest(requDesc);
		syncTrans=srvCon->createTransactionId();
		res=srvCon->startRequest(requDesc, syncTrans);
		syncTrans=0;
		delete requDesc;
	}
	return res;
}

#undef FCT
#define FCT "DataModel::dataMissed(pk)"
void DataModel::dataMissed(PkType entity, PkType pk) const
{
	// TODO feature react on "data missed"
	Q_UNUSED(entity);
	Q_UNUSED(pk);
}

#undef FCT
#define FCT "DataModel::dataMissed(ik)"
void DataModel::dataMissed(PkType entity, PkType ik, TimeType deadline) const
{
	// TODO feature react on "data missed"
	Q_UNUSED(entity);
	Q_UNUSED(ik);
	Q_UNUSED(deadline);
}

#undef FCT
#define FCT "DataModel::getTable"
const DataTable & DataModel::getTable(PkType entity) const
{
	DTsCPkIdx & pks(dts.get<DataTable::PkTag>());
	DTsCPkIdx::const_iterator it(pks.find(entity));
	if(it==pks.end())
		throw new AppException(FCT ": request for unknown entity", true);
	//ctrc(Trc::LOOP,APP,FCT)<<"found table "<<it->getPk()<<" with "<<it->getPayload().dos.size()<<" entries"<<endl;
	return *it;
}

#undef FCT
#define FCT "DataModel::getModTimeSlice"
ModTimeSlice * DataModel::getModTimeSlice(PkType entity)
{
	ModTimeSlices::iterator it(mts.find(entity));
	if(it!=mts.end())
		return it->second;
	ModTimeSlice *res(new ModTimeSlice(entity, this));
	// tricky: if you have a local DB providing data synchronously
	// mts will be scanned (in parseDataSl) when the model
	// is not yet added to the container.
	mts.insert(ModTimeSlices::value_type(entity, res));
	return res;
}

#undef FCT
#define FCT "DataModel::parseDataSl"
void DataModel::parseDataSl(unsigned long transId)
{
	ctrc(Trc::WORK,APP,FCT)<<"start parsing with DataTables "<<&dts<<endl;
	data=QString(srvCon->getData(transId)->data());
	//ctrc(Trc::WORK,APP,FCT)<<data.toStdString().c_str()<<endl;
	QXmlInputSource source(srvCon->getData(transId));
	QXmlSimpleReader reader;
	XmlSrvHandler handler(dts, bootTrans);
	reader.setContentHandler(&handler);
	reader.setErrorHandler(&handler);
	bool res(reader.parse(&source, false));
	if(!res)
		ctrc(Trc::SETUP,APP,FCT)<<"XML parsing failed: "<<handler.errorString().toStdString().c_str()<<endl;
	// If there is a table model attached to a DataTable the row mapping
	// has to be updated after table inserts / removes (but not for changed content)
	// TODO feature update timer - suppress transId if it's from a timed update (and altogether if it was a PK based update and data actually did not change)
	emit serverResponseSig();
	if(transId!=syncTrans) // if the server delivered synchronously we act like the data was always here...
		emit dataChangedSig(transId);
	srvCon->freeData(transId);
	if(bootTrans)
		bootTrans=false;
	if(queueing)
		keepAlive();
}

#undef FCT
#define FCT "DataModel::forceGarbageCollectSl"
void DataModel::forceGarbageCollectSl()
{
	ctrc(Trc::LOOP,APP,FCT)<<"@ tick "<<ticks<<endl;
	tickGarbageFin=ticks+2; // if started through menu a "+1" is unsafe because it means 0..1
	emit statusTextSig(tr("garbage collection..."));
	for(DataTables::const_iterator it(dts.begin()); it!=dts.end(); ++it)
		it->getPayload().clearTouchedAll();
	for(ModTimeSlices::const_iterator it(mts.begin()); it!=mts.end(); ++it)
		it->second->clearTouched();
	emit dataMarkSig();
}

#undef FCT
#define FCT "DataModel::keepAlive"
void DataModel::keepAlive()
{
	if(srvCon->isTesting())
	{
		queueing=true;
		return;
	}
	if(srvCon->acceptTest())
	{
		queueing=true;
		emit statusTextSig(tr("testing server connection..."));
		srvCon->startTest();
		return;
	}
	if(!srvCon->acceptRequest())
		throw new AppException(FCT ": neither test nor request accepted", true);
	return;
}

#undef FCT
#define FCT "DataModel::reduceRequest"
void DataModel::reduceRequest(RequestDescriptor * requDesc)
{
	for(RequestDescriptor::iterator it(requDesc->begin()); it!=requDesc->end(); )
	{
		DataTables::const_iterator dit(dts.find(it->second.entity));
		if(dit==dts.end())
		{ // we don't have the table at all
			ostringstream os;
			os <<FCT<<": unknown table "<<it->second.entity<<endl;
			throw new AppException(os.str().c_str(), true);
		}
		if(!dit->scopeCovered(&it->second))
		{ // not the right objects within
			++it;
			continue;
		}
		// we have it already - increment iterator and erase request
		requDesc->eraseInc(it);
	}
}

#undef FCT
#define FCT "DataModel::dumpRequest"
void DataModel::dumpRequest(RequestDescriptor *requDesc)
{
	QBuffer *b(requDesc->createParamsBuffer());
	b->open(QIODevice::ReadOnly);
	rq=b->readAll();
	b->close();
	delete b;
}

#undef FCT
#define FCT "DataModel::timerTic"
void DataModel::timerTic()
{
	++ticks;
	//ctrc(Trc::LOOP,APP,FCT)<<"tick "<<ticks<<endl;
	if(tickGarbageFin && ticks>=tickGarbageFin)
	{
		ctrc(Trc::LOOP,APP,FCT)<<"finalising garbage collection @ tick "<<ticks<<endl;
		tickGarbageFin=0;
		for(DataTables::const_iterator it(dts.begin()); it!=dts.end(); ++it)
		{
			it->getPayload().cleanGarbage();
		}
		for(ModTimeSlices::iterator it(mts.begin()); it!=mts.end(); )
		{
			if(it->second->isTouched())
				++it;
			else
			{
				ModTimeSlices::iterator delIt(it);
				++it;
				mts.erase(delIt);
			}
		}
		emit statusTextSig(tr("garbage collection - done"));
	}
	else if(!(ticks%ticksUpdate))
		// TODO feature update timer
		;
	else if(!(ticks%ticksGarbage))
		forceGarbageCollectSl();
	else if(!(ticks%ticksAlive))
		keepAlive();
}

#undef FCT
#define FCT "DataModel::checkQueueingSl"
void DataModel::checkQueueingSl(bool testSuccess)
{
	ctrc(Trc::LOOP,APP,FCT)<<" testSuccess "<<testSuccess<<", "<<rqQueue.size()<<" queued data requests"<<endl;
	if(!testSuccess)
	{
		emit statusTextSig(tr("server connection FAILED"));
		return;
	}
	emit statusTextSig(tr("server connection OK"));
	queueing=false;
	if(rqQueue.empty())
		return;
	ctrc(Trc::LOOP,APP,FCT)<<" sending "<<rqQueue.size()<<" queued server requests"<<endl;
	for(RequQueue::iterator it(rqQueue.begin()); it!=rqQueue.end(); ++it)
	{
		dumpRequest(*it);
		srvCon->startRequest(*it, (*it)->getTransId());
		//rd.include(**it);
		delete *it;
	}
	rqQueue.clear();
	return;
}
